#pragma once
#include "Log_test.hpp"
#include <mysql/mysql.h>
#include <memory> //智能指针头文件
#include <jsoncpp/json/json.h>
#include <sstream>
#include <fstream> // 文件流类头文件
// 这里面主要完成对数据库、jSON等类的实现和接口

class mysql_util
{
public:
    static void Mysql_close(MYSQL* _mysql) // 供使用者使用智能指针对该对象进行自动释放。
    {
        cout << "mysql连接已关闭" << endl;
        mysql_close(_mysql);
    }

    static MYSQL* Mysql_create( const char *user, const char *passwd, const char *host, const char *db, 
    unsigned int port, const char *unix_socket = NULL, unsigned long clientflag = 0, const char *character = "utf8") 
    // 初始化及连接mysql服务器, 并且更新导入数据库编码集。
    {
        // 创建一个MYSQL的对象，传入NULL表示让函数分配一个MYSQL对象并返回，如果传入一个地址就表示函数在该地址帮你完成初始化。
        MYSQL* _mysql = mysql_init(NULL);
        if (_mysql == NULL) //_mysql为NULL表示创建失败
        {
            ELOG("mysql_init ERROR");
            return nullptr;
        }

        if (mysql_real_connect(_mysql, host, user, passwd, db, port, unix_socket, clientflag) == NULL)
        {
            ELOG("mysql_real_connect ERROR");
            mysql_close(_mysql);
            return nullptr;
        }

        if (mysql_set_character_set(_mysql, character)) // 该函数返回0表示成功，非0表示失败
        {
            ELOG("mysql_set_character ERROR");
            mysql_close(_mysql);
            return nullptr;
        }
        return _mysql;
    }

    // 执行用户的SQL语句。
    static bool Query(MYSQL* _mysql, const string& DML)
    {
        //cout << DML << endl;
        if (mysql_query(_mysql, DML.c_str()))
        {
            // 执行DMl（sql）语句失败时打印错误信息并且返回false;
            ELOG("mysql_query ERROR %s", mysql_error(_mysql)); // mysql_error返回错误信息。
            return false;
        }
        return true;
    }
    // 供用户选择更改使用数据库
    static bool Changs_db(MYSQL* _mysql, const char *new_databases)
    {
        if (!mysql_select_db(_mysql, new_databases)) // 更改当前使用数据库为new_databases;
        {
            ELOG("mysql_select_db ERROR %d", mysql_error(_mysql));
            return false;
        }
        return true;
    }
};

class Json_api
{
public:
    // 对字符串进行序列化
    static bool serialize(const Json::Value &value, string &Str)
    {
        Json::StreamWriterBuilder swb;
        unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        stringstream ss;
        int ret = sw->write(value, &ss);
        if (ret != 0)
        {
            ELOG("Json_serialize ERROR");
            return false;
        }
        Str = ss.str();
        return true;
    }
    // 对字符串进行反序列化
    static bool unserialize(Json::Value &value, const string &Str)
    {
        string EStr;
        Json::CharReaderBuilder crb;
        unique_ptr<Json::CharReader> cr(crb.newCharReader());
        if (!cr->parse(Str.c_str(), Str.c_str() + Str.size(), &value, &EStr))
        {
            ELOG("Json_unserialize ERROR %s", EStr.c_str());
            return false;
        }
        return true;
    }
};

// 字符串接口类
class String_Util
{
public:
    static void StrSub(const string &Str, const string &sep, vector<string> &arry)
    {
        int pos = 0;
        while (1)
        {
            size_t ret = Str.find(sep, pos); // 查找子串，第一个参数是待查找的子串，第二个参数是开始查找的位置起点。返回值为子串第一次出现的位置。  返回的ret是相较于字符串开始的位置。不是相较于pos而言的
                                             // cout << "ret= " << ret << endl;
            if (ret != std::string::npos)    // 找到子串的情况下进行分割
            {
                if (Str.begin() + pos == Str.begin() + ret) // 两个分割子串连在一起的情况
                {
                    pos = ret + sep.size();
                    continue;
                }
                // 找到了就将中间字符保存至arry中
                arry.push_back({Str.begin() + pos, Str.begin() + ret});
                pos = ret + sep.size(); // 将起始查找位置往后挪。
            }
            else // 没有找到子串的情况下，进行分割并退出
            {
                arry.push_back({Str.begin() + pos, Str.end()});
                return;
            }
        }
    }
};

// 文件读取接口实现
class File_Util
{
public:
    static bool read(const string &filename, string &text)
    {
        // 创建一个ifstream类从文件中读取数据 对应的还有ofstream向文件中写入数据 他们都还有一个父类fstream既可以从文件中读取内容也可以写入内容.
        ifstream ipt;
        ipt.open(filename, ifstream::in | ifstream::binary); // 打开该文件的读权限并且读取流都用2进制读取.防止一些特殊字符读取失败.
        // 检查文件是否被打开.
        if (!ipt.is_open())
        {
            // 如果没有被打开报错并退出.
            ELOG("%s file open ERROR", filename.c_str());
            return false;
        }
        // seekg表示流指针从流的开始端\末端\当前位置偏移至某个位置
        ipt.seekg(0, ifstream::end); // 表示将流中指针移动至文件尾部并且偏移0个位置.
        text.resize(ipt.tellg());    // tellg表示计算文件开始到流中当前指针位置的长度. 并且提前将text的长度大小扩容号,为后面存放数据做好准备.
        ipt.seekg(0, ifstream::beg); // 将流中指针移动至文件最开始位置.
        // 因为read参数一是char* 类型,所以不能直接使用text.c_str()(属于const char* 类型),所以直接提前将需要的大小申请好,后面直接给地址使其按地址赋值.
        // read作用:从流中读取n个字符并且存放到字符串数组中.
        ipt.read(&text[0], text.size());

        if (!ipt.good())
        {
            ELOG("%S file ERROR", filename);
            return false;
        }
        return true;
    }
};
